#include "quickjs/quickjs.h"
#include "utils/str_helper.hpp"
#include "quickjs/env.h"
#include "utils/func_utils.hpp"
#include "register/utility_functions/utility_functions_vararg_method.hpp"
#include "register/utility_functions/register_utility_functions.h"
#include "utils/quickjs_helper.hpp"
#include <godot_cpp/variant/builtin_types.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

{%- set clazz = 'UtilityFunctions' %}
{%- set snake_name = camel_to_snake(clazz) %}
{%- set static_methods = utility_functions | selectattr('is_vararg', 'equalto', false) | list-%}
{%- set vararg_methods = utility_functions | selectattr('is_vararg', 'equalto', true) | list -%}
{%- set clazz_name = 'UtilityFunctions' %}
{%- set norm_name = 'UtilityFunctions' %}
{%- set class_id = norm_name + '::__class_id' %}

using namespace godot;

JSValue {{snake_name}}_instance;

static void {{snake_name}}_class_finalizer(JSRuntime *rt, JSValue val) {
	JSClassID class_id = classes["{{clazz_name}}"];
	{{norm_name}} *{{snake_name}} = static_cast<{{norm_name}} *>(JS_GetOpaque(val, class_id));
	if ({{snake_name}})
		memdelete({{snake_name}});
}

static JSClassDef {{snake_name}}_class_def = {
	"{{clazz_name}}",
	.finalizer = {{snake_name}}_class_finalizer
};

static JSValue {{snake_name}}_class_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) {
	JSClassID class_id = classes["{{clazz_name}}"];
	{{norm_name}} *{{snake_name}}_class;
	JSValue obj = JS_NewObjectClass(ctx, class_id);
	if (JS_IsException(obj))
		return obj;

	{%- if clazz_name in (singletons | map(attribute='type') | list) %}
	{{snake_name}}_class = {{norm_name}}::get_singleton();
	{%- else%}
	{{snake_name}}_class = memnew({{clazz_name}});
	{%- endif %}
	if (!{{snake_name}}_class) {
		JS_FreeValue(ctx, obj);
		return JS_EXCEPTION;
	}

	JS_SetOpaque(obj, {{snake_name}}_class);
	return obj;
}
{%- macro get_const(method) %}
{{-'_const' if method['is_const'] else ''-}}
{%- endmacro %}

{%- for method in  static_methods %}
static JSValue {{snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    {%- if method['return_type'] %}
	return call_builtin_static_method_ret(&{{norm_name}}::{{method['name']}}, ctx, this_val, argc, argv);
    {%- else %}
    call_builtin_static_method_no_ret(&{{norm_name}}::{{method['name']}}, ctx, this_val, argc, argv);
	return JS_UNDEFINED;
    {%- endif %}
};
{%- endfor %}
{%- for method in vararg_methods %}
static JSValue {{snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    {%- if method['return_type'] %}
	return call_builtin_static_vararg_method_ret(&js_{{method['name']}}, ctx, this_val, argc, argv);
    {%- else %}
    return call_builtin_static_vararg_method_no_ret(&js_{{method['name']}}, ctx, this_val, argc, argv);
    {%- endif %}
}
{%- endfor %}

static const JSCFunctionListEntry {{snake_name}}_class_funcs[] = {
	{%- for method in static_methods + vararg_methods %}
	JS_CFUNC_DEF("{{ method['name'] }}", {{method['arguments'] | length}}, &{{snake_name}}_class_{{method['name']}}),
	{%- endfor %}
};

static int js_{{snake_name}}_class_init(JSContext *ctx) {
	classes["{{clazz_name}}"] = 0;
	classes["{{clazz_name}}"] = JS_NewClassID(&classes["{{clazz_name}}"]);
	JSClassID class_id = classes["{{clazz_name}}"];
	class_id_list.insert(class_id);
	JS_NewClass(JS_GetRuntime(ctx), class_id, &{{snake_name}}_class_def);

	JSValue proto = JS_NewObject(ctx);
	{%- if clazz['inherits'] %}
	JSValue base_class = JS_GetClassProto(ctx, class_id);
	JS_SetPrototype(ctx, proto, base_class);
	{%- endif %}
	JS_SetClassProto(ctx, class_id, proto);
	JS_SetPropertyFunctionList(ctx, proto, {{snake_name}}_class_funcs, _countof({{snake_name}}_class_funcs));

	JSValue global = JS_GetGlobalObject(ctx);
	{{snake_name}}_instance = {{snake_name}}_class_constructor(ctx, global, 0, NULL);
	JS_SetPropertyStr(ctx, global, "GD", {{snake_name}}_instance);

	return 0;
}

void register_{{ snake_name }}() {
	js_{{snake_name}}_class_init(ctx);
}